package com.yk.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.yk.api.dataGatherer.dto.Fields;
import com.yk.api.dataGatherer.dto.SuperTableDTO;
import com.yk.api.dataGatherer.model.RemoteTdEngineService;
import com.yk.api.system.dto.DeviceDTO;
import com.yk.api.system.dto.TemplateDTO;
import com.yk.api.system.dto.VariableDTO;
import com.yk.common.core.constant.CacheConstants;
import com.yk.common.core.constant.NumberConstant;
import com.yk.common.core.constant.TdEngIneConstants;
import com.yk.common.core.domain.BasePageQuery;
import com.yk.common.core.domain.Result;
import com.yk.common.core.enums.DataTypeEnum;
import com.yk.common.core.exception.ServiceException;
import com.yk.common.core.utils.LoginHelper;
import com.yk.common.redis.service.RedisService;
import com.yk.system.convert.DeviceConvert;
import com.yk.system.convert.TemplateConvert;
import com.yk.system.convert.VariableConvert;
import com.yk.system.entity.Device;
import com.yk.system.entity.Template;
import com.yk.system.entity.Variable;
import com.yk.system.entity.VariableTemplate;
import com.yk.system.mapper.TemplateMapper;
import com.yk.system.mapper.VariableMapper;
import com.yk.system.mapper.VariableTemplateMapper;
import com.yk.system.service.DeviceService;
import com.yk.system.service.TemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lmx
 * @date 2023/10/24 9:45
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TemplateServiceImpl extends ServiceImpl<TemplateMapper, Template> implements TemplateService {

    private final TemplateConvert templateConvert;
    private final VariableMapper variableMapper;
    private final VariableConvert variableConvert;
    private final VariableTemplateMapper variableTemplateMapper;
    private final RemoteTdEngineService remoteTdEngineService;
    private final RedisService redisService;
    private final DeviceService deviceService;
    private final DeviceConvert deviceConvert;


    @Override
    public int updateBatch(List<Template> list) {
        return baseMapper.updateBatch(list);
    }

    @Override
    public int batchInsert(List<Template> list) {
        return baseMapper.batchInsert(list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(TemplateDTO dto) throws Exception {
        // 模板id
        Long templateId = IdWorker.getId();
        Date date = new Date();
        Long userId = LoginHelper.getLoginUserId();
        dto.setId(templateId);
        // 关联变量
        List<VariableDTO> vbDTOs = dto.getVbVariableDTOS();
        if (CollUtil.isNotEmpty(vbDTOs)) {
            List<Variable> vbs = vbDTOs.stream().map(variableConvert::dto2Entity).collect(Collectors.toList());
            List<VariableTemplate> vts = Lists.newArrayList();
            for (int i = 0; i < vbs.size(); i++) {
                // 变量id
                Long variableId = IdWorker.getId();
                Variable variable = vbs.get(i);
                variable.setId(variableId);
                variable.setSort(i);
                variable.setCreatedBy(userId);
                variable.setCreatedAt(date);
                if (StrUtil.isEmpty(variable.getDataType())){
                    variable.setDataType(NumberConstant.ZERO_STR);
                }
                if (StrUtil.isEmpty(variable.getReset())){
                    variable.setReset(NumberConstant.ZERO_STR);
                }
                vts.add(new VariableTemplate(variableId, templateId));
            }
            // 差值变量
            computeVS(vbs);
            // 内部变量
            computeNb(vbs);
            variableMapper.batchInsert(vbs);
            variableTemplateMapper.batchInsert(vts);
            createSuperTableAndCache(templateId, vbs);
        }
        dto.setVariableCount(vbDTOs.size());
        baseMapper.insert(templateConvert.dto2Entity(dto));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(TemplateDTO dto) throws Exception {
        Long templateId = dto.getId();
        Date date = new Date();
        Long userId = LoginHelper.getLoginUserId();
        // 删除变量
        deleteVariable(dto.getVariableIds(), templateId);
        // 关联变量
        List<VariableDTO> vbDTOs = dto.getVbVariableDTOS();
        if (CollUtil.isEmpty(vbDTOs) && CollUtil.isEmpty(dto.getVariableIds())) {
            return;
        }
        List<VariableTemplate> vts = Lists.newArrayList();
        // 待添加列
        List<Variable> tdAddVbs = Lists.newArrayList();
        // 待删除列
        List<Variable> tdDelVbs = Lists.newArrayList();
        // 待入库
        List<Variable> addVbs = Lists.newArrayList();
        // 待更新
        List<Variable> updateVbs = Lists.newArrayList();
        for (int i = 0; i < vbDTOs.size(); i++) {
            Variable vb = variableConvert.dto2Entity(vbDTOs.get(i));
            vb.setSort(i);
            // 变量的数据类型必须有默认值
            if (StrUtil.isEmpty(vb.getDataType())){
                vb.setDataType(NumberConstant.ZERO_STR);
            }
            // 是否复位
            if (StrUtil.isEmpty(vb.getReset())){
                vb.setReset(NumberConstant.ZERO_STR);
            }
            if (Objects.isNull(vb.getId())) {
                Long variableId = IdWorker.getId();
                vb.setId(variableId);
                vb.setCreatedBy(userId);
                vb.setCreatedAt(date);

                addVbs.add(vb);
                tdAddVbs.add(vb);
                VariableTemplate vbVariableTemplate = new VariableTemplate(variableId, templateId);
                vts.add(vbVariableTemplate);
            } else {
                vb.setUpdatedBy(userId);
                vb.setUpdatedAt(date);
                Variable oldVb = variableMapper.selectById(vb.getId());
                if (!oldVb.getStorage().equals(vb.getStorage())) {
                    if (NumberConstant.ONE_STR.equals(vb.getStorage())) {
                        // 从不存储改为存储
                        tdAddVbs.add(vb);
                    } else {
                        // 从存储改为不存储
                        tdDelVbs.add(vb);
                    }
                }
                // 变量地址修改
                if (!oldVb.getAddress().equals(vb.getAddress())) {
                    tdAddVbs.add(vb);
                    tdDelVbs.add(oldVb);
                }
                updateVbs.add(vb);
            }
        }
        List<Variable> allVbs = Lists.newArrayList();
        allVbs.addAll(addVbs);
        allVbs.addAll(updateVbs);
        // 差值变量
        computeVS(allVbs);
        // 内部变量
        computeNb(allVbs);
        // 批量插入
        if (CollUtil.isNotEmpty(addVbs)) {
            variableMapper.batchInsert(addVbs);
        }
        // 批量更新
        if (CollUtil.isNotEmpty(updateVbs)) {
            variableMapper.updateBatch(updateVbs);
        }
        // 批量保存关联关系
        if (CollUtil.isNotEmpty(vts)) {
            variableTemplateMapper.batchInsert(vts);
        }
        // 添加更新的数据到addVbs列表中
        addVbs.addAll(updateVbs);
        try {
            // 超级表添加列
            addColumnForSuperTable(templateId, tdAddVbs);
            // 超级表删除列
            dropColumnForSuperTable(templateId, tdDelVbs, dto.getVariableIds(), tdAddVbs);
            // 构建超级表DTO对象
            SuperTableDTO superTableDto = buildSuperTable(templateId, addVbs);
            // 缓存超级表DTO对象到Redis中
            updateRedis(superTableDto);
        } catch (Exception e) {
            log.error("超级表添加列失败: {}", e.getMessage());
            // 原有变量从不存储变成存储的情况
            tdAddVbs.addAll(updateVbs);
            createSuperTableAndCache(templateId, tdAddVbs);
        }
        dto.setVariableCount(addVbs.size());
        baseMapper.updateById(templateConvert.dto2Entity(dto));
    }

    @Override
    public IPage<TemplateDTO> queryPage(BasePageQuery<TemplateDTO> pageParam) {
        IPage<TemplateDTO> page = baseMapper.queryPage(new Page<>(pageParam.getPageNum(), pageParam.getPageSize()), pageParam.getParam());
        if (CollUtil.isNotEmpty(page.getRecords())) {
            page.getRecords().forEach(it -> {
                // 关联变量
                List<Variable> vbs = variableMapper.queryDtoByTemplateId(it.getId());
                if (CollUtil.isNotEmpty(vbs)) {
                    List<VariableDTO> vbDTOs = vbs.stream().map(variableConvert::entity2Dto).collect(Collectors.toList());
                    it.setVbVariableDTOS(vbDTOs);
                }

                // 关联设备
                List<Device> list = deviceService.list(new LambdaQueryWrapper<Device>().eq(Device::getTemplateId, it.getId()));
                if (CollUtil.isNotEmpty(list)) {
                    List<DeviceDTO> deviceDTOS = list.stream().map(deviceConvert::entity2Dto).collect(Collectors.toList());
                    it.setDeviceDTOList(deviceDTOS);
                }
            });
        }
        return page;
    }

    @Override
    public TemplateDTO view(Long id) {
        Template template = baseMapper.selectById(id);
        if (Objects.isNull(template)) {
            return null;
        }
        TemplateDTO dto = templateConvert.entity2Dto(template);
        List<Variable> vbs = variableMapper.queryDtoByTemplateId(id);
        if (CollUtil.isNotEmpty(vbs)) {
            List<VariableDTO> vbDTOs = vbs.stream().map(variableConvert::entity2Dto).collect(Collectors.toList());
            dto.setVbVariableDTOS(vbDTOs);
        }
        return dto;
    }

    @Override
    public void removeCache(List<Long> templateIds) {
        if (CollUtil.isEmpty(templateIds)) {
            return;
        }
        templateIds.forEach(it -> {
            String key = CacheConstants.SUPER_TABLE_FIELD_KEY + TdEngIneConstants.STABLE_PREFIX + it;
            redisService.deleteObject(key);
        });
    }

    @Override
    public void initTempRedis() throws Exception {
        List<Template> list = this.list(new LambdaQueryWrapper<>());
        if (CollUtil.isEmpty(list)) {
            return;
        }

        list.forEach(it -> {
            List<Variable> variableList = variableMapper.queryByTemplateId(it.getId());
            if (CollUtil.isEmpty(variableList)) {
                return;
            }
            // 构建
            SuperTableDTO superTableDto = buildSuperTable(it.getId(), variableList);
            // 创建表信息 tdengine表丢失的时候才打开运行。
            /*try {
                if (superTableDto.getPtFields().isEmpty()){
                    return;
                }
                createSuperTable(superTableDto);
            } catch (Exception e) {
                log.error("创建超级表失败", e);
            }*/
            // 缓存
            try {
                updateRedis(superTableDto);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 初始创建超级表结构
     *
     * @param tempId 模板id
     * @param vbs    变量列表
     */
    public SuperTableDTO buildSuperTable(Long tempId, List<Variable> vbs) {
        SuperTableDTO superTableDto = new SuperTableDTO();
        if (CollUtil.isEmpty(vbs)) {
            return superTableDto;
        }
        // 去重一下重复的变量地址
        List<Variable> uniqueVbs = vbs.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Variable::getAddress))),
                        ArrayList::new));
        // 超级表名称命名规则 ST_变量模型ID
        superTableDto.setSuperTableName(TdEngIneConstants.STABLE_PREFIX + tempId);
        // 构建超级表的表结构字段列表
        List<Fields> fieldsList = CollUtil.newArrayList();
        // 超级表第一个字段数据类型必须为时间戳,默认Ts为当前系统时间
        Fields ts = new Fields(TdEngIneConstants.TS, DataTypeEnum.TIMESTAMP.getDataType(), null);
        ts.setStorage(Boolean.TRUE);
        ts.setShow(Boolean.TRUE);
        ts.setVariableDataType(NumberConstant.ZERO_STR);
        fieldsList.add(ts);
        uniqueVbs.forEach(it -> {
            Fields fields = new Fields(it.getAddress(), DataTypeEnum.NCHAR.getDataType(), 50);
            // 是否存储
            if (Objects.equals(NumberConstant.ZERO.toString(), it.getStorage())) {
                fields.setStorage(Boolean.FALSE);
            } else {
                fields.setStorage(Boolean.TRUE);
            }
            // 是否展示
            if (Objects.equals(NumberConstant.ZERO.toString(), it.getShow())) {
                fields.setShow(Boolean.FALSE);
            } else {
                fields.setShow(Boolean.TRUE);
            }
            fields.setVariableId(it.getId());
            fields.setType(it.getType());
            fields.setFormula(it.getFormula());
            fields.setDecimal(it.getDecimal());
            fields.setName(it.getName());
            fields.setUnit(it.getUnit());
            fields.setVariableDataType(it.getDataType());
            // 内部变量
            fields.setInterior(new JSONObject(it.getInterior()));
            fields.setComputeType(it.getComputeType());
            fields.setCycleFrequency(it.getCycleFrequency());
            fields.setParentId(it.getParentId());
            fields.setParentAddress(it.getParentAddress());
            fieldsList.add(fields);
        });
        // 普通变量
        List<Fields> ptFields = fieldsList.stream()
                .filter(it -> NumberConstant.ZERO_STR.equals(it.getVariableDataType())).collect(Collectors.toList());
        superTableDto.setPtFields(ptFields);
        // 差值变量
        List<Fields> czFields = fieldsList.stream()
                .filter(it -> NumberConstant.ONE_STR.equals(it.getVariableDataType())).collect(Collectors.toList());
        superTableDto.setCzFields(czFields);
        // 内部变量
        List<Fields> nbFields = fieldsList.stream()
                .filter(it -> NumberConstant.TWO_STR.equals(it.getVariableDataType())).collect(Collectors.toList());
        superTableDto.setNbFields(nbFields);
        // 构建超级表标签字段列表
        List<Fields> tagsFields = CollUtil.newArrayList();
        tagsFields.add(new Fields(TdEngIneConstants.DEVICE_ID, DataTypeEnum.BIGINT.getDataType(), 20));
        tagsFields.add(new Fields(TdEngIneConstants.DEVICE_NUM, DataTypeEnum.NCHAR.getDataType(), 20));
        superTableDto.setTagsFields(tagsFields);
        return superTableDto;
    }

    /**
     * 删除变量、变量关联模版
     */
    private void deleteVariable(List<Long> ids, Long templateId) throws Exception {
        if (CollUtil.isNotEmpty(ids)) {
            variableMapper.deleteBatchIds(ids);
            variableTemplateMapper.delete(new LambdaQueryWrapper<VariableTemplate>()
                    .eq(VariableTemplate::getTemplateId, templateId)
                    .in(VariableTemplate::getVariableId, ids));
        }
    }

    /**
     * 创建超级表并缓存
     *
     * @param templateId 模版id
     * @param vbs        变量列表
     * @throws Exception
     */
    public void createSuperTableAndCache(Long templateId, List<Variable> vbs) throws Exception {
        // 构建超级表DTO对象
        SuperTableDTO superTableDto = buildSuperTable(templateId, vbs);
        // 创建超级表
        createSuperTable(superTableDto);
        // 缓存超级表DTO对象到Redis中
        updateRedis(superTableDto);
    }

    /**
     * 创建超级表
     */
    private void createSuperTable(SuperTableDTO dto) throws Exception {
        try {
            Result<?> superTable = remoteTdEngineService.createSuperTable(dto);
            if (!superTable.isSuccess()) {
                throw new ServiceException("超级表创建失败: " + superTable.getMsg());
            }
        } catch (Exception e) {
            throw new ServiceException("超级表创建失败: " + e.getMessage());
        }
    }

    /**
     * 超级表添加列字段
     *
     * @param tempId 模版id
     * @param vbs    变量列表
     */
    private void addColumnForSuperTable(Long tempId, List<Variable> vbs) throws Exception {
        if (CollUtil.isEmpty(vbs)) {
            return;
        }
        SuperTableDTO superTableDto = new SuperTableDTO();
        // 超级表名称命名规则 ST_变量模型ID
        superTableDto.setSuperTableName(TdEngIneConstants.STABLE_PREFIX + tempId);
        vbs.forEach(it -> {
            if (Objects.equals(NumberConstant.ZERO.toString(), it.getStorage())) {
                return;
            }
            superTableDto.setFields(new Fields(it.getAddress(), "string", 20));
            Result<?> result = remoteTdEngineService.addColumnForSuperTable(superTableDto);
            if (!result.isSuccess()) {
                throw new ServiceException("超级表添加列失败: " + result.getMsg());
            }
        });
    }

    /**
     * 超级表删除列字段
     *
     * @param tempId      模版id
     * @param vbs         变更变量列表
     * @param variableIds 删除变量列表
     * @param addVb       新增的变量列表，当删除的变量地址和新增的相同时，不进行删除
     */
    private void dropColumnForSuperTable(Long tempId, List<Variable> vbs, List<Long> variableIds, List<Variable> addVb) throws Exception {
        if (CollUtil.isEmpty(vbs) && CollUtil.isEmpty(variableIds)) {
            return;
        }
        SuperTableDTO superTableDto = new SuperTableDTO();
        // 超级表名称命名规则 ST_变量模型ID
        superTableDto.setSuperTableName(TdEngIneConstants.STABLE_PREFIX + tempId);
        // 变更变量列表
        if (CollUtil.isNotEmpty(vbs)) {
            vbs.forEach(it -> {
                try {
                    superTableDto.setFields(new Fields(it.getAddress(), "", null));
                    remoteTdEngineService.dropColumnForSuperTable(superTableDto);
                } catch (Exception e) {
                    log.error("超级表删除列失败: " + it.getAddress());
                }
            });
        }
        // 删除变量列表
        if (CollUtil.isEmpty(variableIds)) {
            return;
        }
        List<Variable> variables = variableMapper.getByIdsIgnoreDelete(variableIds);
        if (CollUtil.isEmpty(variables)) {
            return;
        }
        List<String> newAddressList = addVb.stream().map(Variable::getAddress).collect(Collectors.toList());
        variables.forEach(it -> {
            try {
                if (newAddressList.contains(it.getAddress())) {
                    return;
                }
                superTableDto.setFields(new Fields(it.getAddress(), "", null));
                remoteTdEngineService.dropColumnForSuperTable(superTableDto);
            } catch (Exception e) {
                log.error("超级表删除列失败: " + it.getAddress());
            }
        });
    }

    /**
     * 更新redis变量模板信息
     *
     * @param dto 创建超级表需要的入参的实体类
     */
    public void updateRedis(SuperTableDTO dto) throws Exception {
        String superTableName = dto.getSuperTableName();
        redisService.setCacheObject(CacheConstants.SUPER_TABLE_FIELD_KEY + superTableName, dto);
    }

    /**
     * 差值变量处理
     *
     * @param vbs 变量列表
     */
    private void computeVS(List<Variable> vbs) {
        List<Variable> computeVS = vbs.stream().filter(it -> NumberConstant.ONE_STR.equals(it.getDataType()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(computeVS)) {
            Map<String, Long> map = vbs.stream()
                    .filter(it -> NumberConstant.ZERO_STR.equals(it.getDataType()))
                    .collect(Collectors.toMap(Variable::getAddress, Variable::getId));
            for (Variable it : computeVS) {
                Long parentId = map.get(it.getParentAddress());
                if (Objects.isNull(parentId)) {
                    throw new ServiceException("差值变量的父变量不存在");
                }
                it.setParentId(parentId);
                it.setDataType(NumberConstant.ONE_STR);
                Variable variable = variableMapper.selectById(it.getId());
                if (Objects.isNull(variable)) {
                    it.setAddress(String.valueOf(IdWorker.getId()));
                }
            }
        }
    }

    /**
     * 内部变量处理
     *
     * @param vbs 变量列表
     */
    private void computeNb(List<Variable> vbs) {
        List<Variable> computeNb = vbs.stream().filter(it -> NumberConstant.TWO_STR.equals(it.getDataType()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(computeNb)) {
            for (Variable it : computeNb) {
                it.setDataType(NumberConstant.TWO_STR);
                Variable variable = variableMapper.selectById(it.getId());
                if (Objects.isNull(variable)) {
                    it.setAddress(String.valueOf(IdWorker.getId()));
                }
            }
        }
    }
}
